package com.ironman.imes.device.framework.web.aspect;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.ironman.imes.device.framework.web.LogContent;
import com.ironman.imes.device.framework.web.adapter.WhitespaceRemovalAdapter;
import com.ironman.imes.device.framework.web.annotation.WebLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Collectors;


/**
 * description
 * company 铁人科技
 *
 * @author 陈浩杰
 * @date 2024/7/4 8:37
 */
@Slf4j
@Aspect
@Component
public class WebLogAspect {

    private static final Gson GSON = new GsonBuilder()
            .registerTypeAdapter(String.class, new WhitespaceRemovalAdapter())
            .setDateFormat("yyyy-MM-dd HH:mm:ss").create();

    @Pointcut("@annotation(com.ironman.imes.device.framework.web.annotation.WebLog) || @within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Controller)")
    public void webLog() {
    }

    /**
     * 环绕
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        WebLog webLogAnnotation = Optional.ofNullable(method.getAnnotation(WebLog.class)).orElse(proceedingJoinPoint.getTarget().getClass().getAnnotation(WebLog.class));
        if (webLogAnnotation != null && !webLogAnnotation.record()) {
            return proceedingJoinPoint.proceed();
        }

        Parameter[] parameters = method.getParameters();
        String params = Arrays.stream(parameters).map(n -> n.getType().getSimpleName() + " " + n.getName()).collect(Collectors.joining(","));

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(attributes)) {
            return proceedingJoinPoint.proceed();
        }
        Object[] args = proceedingJoinPoint.getArgs();
        HttpServletRequest request = attributes.getRequest();
        List<String> headerList = new ArrayList<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headerList.add(StrUtil.format("{}={}", headerName, request.getHeader(headerName)));
        }
        LogContent logContent = LogContent.builder()
                .webLog(webLogAnnotation)
                .beginTime(DateUtil.now())
                .uri(request.getRequestURI())
                .httpMethod(request.getMethod())
                .classMethod(StrUtil.format("{}.{}.({})", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName(), params))
                .ip(request.getRemoteAddr())
                .header(String.join(";", headerList))
                .requestArgs(GSON.toJson(args))
                .build();

        Object result;
        try {
            long startTime = System.currentTimeMillis();
            result = proceedingJoinPoint.proceed();
            logContent.setTimeConsuming(System.currentTimeMillis() - startTime);
            logContent.setResponseArgs(GSON.toJson(result));
        } catch (Throwable tx) {
            logContent.setError(tx);
            throw tx;
        } finally {
            print(logContent);
        }
        return result;
    }

    private static void print(LogContent logContent) {
        Throwable error = logContent.getError();
        if (error == null) {
            log.info("\n" +
                            "BeginTime          :{}\n" +
                            "URI                :{}\n" +
                            "HTTP Method        :{}\n" +
                            "Class Method       :{}\n" +
                            "IP                 :{}\n" +
                            "Header             :{}\n" +
                            "Request Args       :{}\n" +
                            "Response Args      :{}\n" +
                            "Time-Consuming(ms) :{}",
                    logContent.getBeginTime(),
                    logContent.getUri(),
                    logContent.getHttpMethod(),
                    logContent.getClassMethod(),
                    logContent.getIp(),
                    logContent.getHeader(),
                    logContent.getRequestArgs(),
                    logContent.getResponseArgs(),
                    logContent.getTimeConsuming()
            );
        } else {
            log.info("\n" +
                            "BeginTime          :{}\n" +
                            "URI                :{}\n" +
                            "HTTP Method        :{}\n" +
                            "Class Method       :{}\n" +
                            "IP                 :{}\n" +
                            "Header             :{}\n" +
                            "Request Args       :{}\n" +
                            "Error Detail       :",
                    logContent.getBeginTime(),
                    logContent.getUri(),
                    logContent.getHttpMethod(),
                    logContent.getClassMethod(),
                    logContent.getIp(),
                    logContent.getHeader(),
                    logContent.getRequestArgs(),
                    logContent.getError()
            );
        }
    }
}